package com.example.myapplication;

import java.nio.channels.SocketChannel;
import java.nio.channels.ServerSocketChannel;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;

 class streambase {
    SocketChannel outSo;
    double staTime = 0;
    Map<Short, sendPackMapData> sendPackMap = new HashMap<Short, sendPackMapData>();
    Map<Short, byte[]> recPackMap = new HashMap<Short, byte[]>();

    short sendSelfPos = 0, sendPeerPos = 0, recPos = 0;
    double sendStatusRate;
    Map<Short, Integer> notRecInfo = new HashMap<Short, Integer>();
    Map<Short, Integer> peerNotRecInfo = new HashMap<Short, Integer>();
    double statusGapTime = Double.MAX_VALUE;
    short pushAhead;

    double newPeerTime = -Double.MAX_VALUE;
    double peerMyTime = -Double.MAX_VALUE;

    boolean hasStart = false;
    double maxSend = 1;
    double slope = 1;
    int packLimit;
    double updatedTime = helpFunc.getTime();
    int calStaNum = 0;
    double waitingTime = 0;

    float rRaw, wRaw, rNet, wNet;

    float totalRec, blankRec, totalSend, blankSend, statusSend, statusRev;
    boolean isServer;
    Map<String, packStaData> maxSendL = new HashMap<String, packStaData>();
    Map<String, packStaData> maxRec = new HashMap<String, packStaData>();
    Map<String, packStaData> peerMaxSend = new HashMap<String, packStaData>();
    Map<String, packStaData> peerMaxRec = new HashMap<String, packStaData>();

    byte[] readBuffer = new byte[0];
    byte[] writeBuffer = new byte[0];
    ServerSocketChannel listSo;
    int listenPort;

    streambase( int pushAhead_arg, int packLimit_arg, boolean isServer_arg) throws Exception {
        sendStatusRate = helpFunc.con_minRate;
        packLimit = packLimit_arg;
        pushAhead =(short) pushAhead_arg;
        isServer = isServer_arg;
        for (short i : helpFunc.circleRange((short) 0, pushAhead)) {
            notRecInfo.put(i, 0);
        }
        listSo = ServerSocketChannel.open();
        InetSocketAddress add = new InetSocketAddress("localhost", 0);
        listSo.bind(add);
        InetSocketAddress tt = (InetSocketAddress) listSo.getLocalAddress();
        listenPort = tt.getPort();
    }

    public void makeOutSo() throws Exception {
        outSo = listSo.accept();
        listSo.close();
        outSo.configureBlocking(false);
    }

    public void dealStatusBack(byte[] re) {
        updatedTime = helpFunc.getTime();
        structWrapper s = new structWrapper(re);
        s.readByte();
        double peerTime = s.readDouble();
        if (peerTime < newPeerTime)
            return;
        newPeerTime = peerTime;
        double peerMyTime_h = s.readDouble();
        peerMyTime = peerMyTime_h;
        if (isServer) {
            sendStatusRate = s.readDouble();
            slope = s.readDouble();
            maxSend = s.readDouble();
            waitingTime = s.readDouble();
        } else {
            s.readDouble();
            s.readDouble();
            s.readDouble();
            s.readDouble();
        }
        short pms = s.readWord();
        short pmr = s.readWord();
        helpFunc.addPackSta(peerMaxSend, pms, peerMyTime_h + statusGapTime / 2);
        helpFunc.addPackSta(peerMaxRec, pmr, peerMyTime_h + statusGapTime / 2);
        short peerPos = s.readWord();
        peerNotRecInfo.clear();
        sendPeerPos = peerPos;
        byte[] st = s.getLeftData();
        String ss = new String();
        int ra = st.length;
        for (int i = 0; i < ra; i++) {
            byte j = st[i];
            String s2 = Integer.toBinaryString(j);
            String zo = "0";
            ss += helpFunc.getMulString(zo, 8 - s2.length()) + s2;

        }
        int co = 0;
        for (short one : helpFunc.circleRange(peerPos, helpFunc.circleAdd(peerPos, pushAhead))) {
            if (ss.charAt(co) == '1') {
                peerNotRecInfo.put(one, 0);
            }
            co += 1;
        }

    }

    public void dealPushBack(byte[] re) {
        structWrapper s = new structWrapper(re);
        s.readByte();
        short num = s.readWord();
        if (notRecInfo.containsKey(num)) {
            notRecInfo.remove(num);
            recPackMap.put(num, s.getLeftData());
        }
    }

    public void deal_data_back(ArrayList<byte[]> l) {
        for (byte[] re : l) {
            byte ty = re[0];
            totalRec += 1;
            if (ty == 0) {
                dealStatusBack(re);
                statusRev += 1;
            } else if (ty == 1) {
                dealPushBack(re);
            } else {
                blankRec += 1;
            }

        }
        if (peerMyTime != -Double.MAX_VALUE) {
            statusGapTime = helpFunc.getTime() - peerMyTime;
            if (!hasStart) {
                hasStart = true;
                helpFunc.print("got conn");
            }
        }
    }




    public void read() {
        if (!hasStart) {
            return;
        }
        if (!recPackMap.containsKey(recPos))
            return;
        int addL = 0;
        int bufferL = readBuffer.length;
        ArrayList<byte[]> tempL = new ArrayList<byte[]>();
        ArrayList<Short> l = helpFunc.circleRange(recPos, helpFunc.circleAdd(recPos, pushAhead));
        for (short one : l) {
            if (!recPackMap.containsKey(one))
                break;
            if (bufferL + addL > helpFunc.con_streamBufferSize)
                break;
            tempL.add(recPackMap.get(one));
            addL += recPackMap.get(one).length;

            recPackMap.remove(one);
            recPos = helpFunc.circleAddOne(one);
            notRecInfo.put(helpFunc.circleAdd(one, pushAhead), 0);
        }
        if (addL > 0) {
            byte[] newB = new byte[bufferL + addL];
            int destPos = 0;
            System.arraycopy(readBuffer, 0, newB, destPos, bufferL);
            destPos += bufferL;

            for (byte[] one : tempL) {
                System.arraycopy(one, 0, newB, destPos, one.length);
                destPos += one.length;
            }
            readBuffer = newB;
            rNet += addL;
        }

    }

    void write() {
        if (!hasStart) {
            return;
        }
        int lB = writeBuffer.length;
        if(lB==0)return;
        int newPos = 0;
        while (true) {
            if (lB == 0)
                break;
            if (helpFunc.circleBig(helpFunc.circleAdd(sendPeerPos, pushAhead), sendSelfPos) == sendSelfPos)
                break;
            byte[] s1;
            if (newPos + packLimit < writeBuffer.length) {
                s1 = new byte[packLimit];
                System.arraycopy(writeBuffer, newPos, s1, 0, packLimit);
            } else {

                s1 = new byte[writeBuffer.length - newPos];
                System.arraycopy(writeBuffer, newPos, s1, 0, writeBuffer.length - newPos);
            }
            newPos += packLimit;
            sendPackMapData d = new sendPackMapData();
            d.con = s1;
            sendPackMap.put(sendSelfPos, d);
            wNet += s1.length;
            sendSelfPos = helpFunc.circleAddOne(sendSelfPos);
            if (newPos >= lB)
                break;
        }
        if (writeBuffer.length - newPos <= 0) {
            writeBuffer = new byte[0];
            return;
        }

        byte[] newW = new byte[writeBuffer.length - newPos];
        System.arraycopy(writeBuffer, newPos, newW, 0, writeBuffer.length - newPos);

        writeBuffer = newW;
    }
    public ArrayList<byte[]> get_data_to_send(int n) {
        totalSend += n;
        clearRecording();
        int statusNum = 0;
        if (!hasStart)
            statusNum = n;
        else {
            calStaNum += n;
            statusNum = (int) (calStaNum * sendStatusRate);
            if (statusNum > n)
                statusNum = n;
            calStaNum -= statusNum / sendStatusRate;
        }
        byte[] st = getOneStatus();
        ArrayList<byte[]> l = findNPack(n - statusNum);
        statusSend += statusNum;
        ArrayList<byte[]> ret = new ArrayList<byte[]>();
        for (int i = 0; i < statusNum; i++) {
            ret.add(st);
        }
        ret.addAll(l);
        for (int i = 0; i < n - statusNum - l.size(); i++) {
            byte[] re = { 2 };
            ret.add(re);
            blankSend += 1;
        }
        return ret;
    }

    byte[] getOneStatus() {
        structWrapper re = new structWrapper(1400);
        re.writeByte((byte) 0);
        re.writeDouble(helpFunc.getTime());
        re.writeDouble(newPeerTime);

        re.writeDouble(sendStatusRate);
        re.writeDouble(slope);
        re.writeDouble(maxSend);
        re.writeDouble(waitingTime);

        re.writeWord(helpFunc.getPackStaBigV(maxSendL));
        re.writeWord(helpFunc.getPackStaBigV(maxRec));
        re.writeWord(recPos);
        int co = 0;
        String ss = new String();
        ArrayList<Short> l = helpFunc.circleRange(recPos, helpFunc.circleAdd(recPos, pushAhead));
        for (short i : l) {
            if (!notRecInfo.containsKey(i))
                ss += "0";
            else {
                ss += "1";
            }
            co += 1;
            if (co == 8) {
                re.writeByte((byte) Integer.parseInt(ss, 2));
                co = 0;
                ss = new String();
            }
        }
        String zo = "0";
        ss += helpFunc.getMulString(zo, 8 - ss.length());
        re.writeByte((byte) Integer.parseInt(ss, 2));
        return re.getWriteData();
    }
    void clearRecording() {
        for (short i : helpFunc.circleRange(sendPeerPos, sendSelfPos)) {
            if (!peerNotRecInfo.containsKey(i))
                continue;
            Map<String, Double> m = sendPackMap.get(i).sendRecording;
            ArrayList<String> ll = new ArrayList<String>(m.keySet());
            for (String k : ll) {
                double v = m.get(k);
                if (v < helpFunc.getTime() - statusGapTime - waitingTime)
                    sendPackMap.get(i).sendRecording.remove(k);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        System.currentTimeMillis();

    }
    int findMinTimes(Map<Integer, tempMData> m) {
        double mi = Double.MAX_VALUE;
        int k = -1;
        for (int i : m.keySet()) {
            tempMData j = m.get(i);
            if (j.times < mi && j.realTimes < maxSend) {
                mi = j.times;
                k = i;
            }
        }
        return k;
    }

    ArrayList<byte[]> findNPack(int n) {
        ArrayList<byte[]> ret = new ArrayList<byte[]>();
        if (n == 0)
            return ret;
        Map<Integer, tempMData> m = new HashMap<Integer, tempMData>();
        int co = 0;
        ArrayList<Short> l = helpFunc.circleRange(sendPeerPos, sendSelfPos);
        for (short i : l) {
            if (!peerNotRecInfo.containsKey(i))
                continue;
            tempMData d = new tempMData();
            d.originNum = i;
            d.times = sendPackMap.get(i).sendRecording.size();
            d.realTimes = sendPackMap.get(i).sendRecording.size();
            m.put(co, d);
            co += 1;
        }
        int l2 = m.size();
        if (l2 == 0)
            return ret;
        double perGap = slope / l2;
        for (int k : m.keySet()) {
            m.get(k).times += k * perGap;//?
        }
        while (true) {
            int k = findMinTimes(m);
            if (k == -1)
                return ret;
            m.get(k).times += 1;
            m.get(k).realTimes += 1;
            String u = UUID.randomUUID().toString();
            short ori = m.get(k).originNum;
            sendPackMap.get(ori).sendRecording.put(u, helpFunc.getTime());
            byte[] re = new byte[3 + sendPackMap.get(ori).con.length];
            re[0] = 1;
            System.arraycopy(ByteBuffer.allocate(2).order(ByteOrder.nativeOrder()).putShort((short) ori).array(), 0, re,
                    1, 2);
            System.arraycopy(sendPackMap.get(ori).con, 0, re, 3, sendPackMap.get(ori).con.length);
            ret.add(re);
            n -= 1;
            if (n == 0)
                return ret;
        }
    }





}
